Skip to content

feat(plugin-axios,plugin-fetch): add unwrap() to every generated call - #820

Merged
stijnvanhulle merged 9 commits into
mainfrom
claude/client-unwrap-method
Sep 2, 2026
Merged

feat(plugin-axios,plugin-fetch): add unwrap() to every generated call#820
stijnvanhulle merged 9 commits into
mainfrom
claude/client-unwrap-method

Conversation

@stijnvanhulle

@stijnvanhulle stijnvanhulle commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

🎯 Changes

An alternative to the returnType option in #818, requested in kubb-labs/discussions#3952: a way to get a generated call's success body without destructuring { data } at every call site.

This takes the Redux Toolkit approach instead of a plugin-wide default. Every generated call (standalone function or SDK method) now resolves to a promise with an extra unwrap() method. unwrap() resolves to the bare success body, or rejects with error for a result that carried one (only reachable with throwOnError: false, since the throwing path never resolves an error).

const pet = await getPetById({ path: { petId: 1 } }).unwrap()

await getPetById(...) still resolves to the full { status, data, error, contentType, request, response } result, exactly as before. There's no option to turn this on or off, it's just there. That also means it composes cleanly with the query plugins: @kubb/plugin-react-query, @kubb/plugin-vue-query, @kubb/plugin-swr, and @kubb/plugin-mcp all call the generated client directly and await it, so their generated hooks are byte-for-byte unchanged.

Built independently of #818's returnType option (this branch is off main, not stacked on it), so the two designs can be reviewed and land separately.

How it works

  • plugin-axios/plugin-fetch runtime templates (templates/*.ts) add UnwrapResult, Unwrappable, and a withUnwrap() helper that attaches .unwrap() to a call's result promise.
  • internals/client: buildResultType now always wraps RequestResult in Unwrappable, and buildReturnStatement wraps every call in withUnwrap(...). Both the standalone functions and the class-based SDK pick this up automatically.
  • Every tests/3.0.x e2e snapshot and package-level generator snapshot that embeds a generated operation or .kubb/client.ts was regenerated. I checked the diff by hand: it's mechanical.

✅ Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested this code locally with pnpm run test.

🚀 Release Impact

  • This change affects published code, and I have generated a changeset.
  • This change is for the docs (no release).

🤖 Generated with Claude Code

https://claude.ai/code/session_012DSVW79h2FdFDkoWWr6PwN

kubb-labs/discussions#3952 asked for a way to get a generated call's
success body without destructuring { data } at every call site.
Rather than a plugin-wide returnType default (see PR #818), take the
Redux-Toolkit approach: every call's promise gains an unwrap() method,
resolving to the bare success body or rejecting with `error`. Plain
`await getPetById(...)` keeps returning the full result, so existing
code and the query plugins (react-query, vue-query, swr, mcp) are
unaffected.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012DSVW79h2FdFDkoWWr6PwN
@codesandbox

codesandbox Bot commented Sep 1, 2026

Copy link
Copy Markdown

Review or Edit in CodeSandbox

Open the branch in Web EditorVS CodeInsiders

Open Preview

@changeset-bot

changeset-bot Bot commented Sep 1, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 5264b05

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 5 packages
Name Type
@kubb/plugin-axios Minor
@kubb/plugin-fetch Minor
@kubb/plugin-react-query Patch
@kubb/plugin-vue-query Patch
@kubb/plugin-swr Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new

pkg-pr-new Bot commented Sep 1, 2026

Copy link
Copy Markdown
More templates

@kubb/plugin-axios

npm i https://pkg.pr.new/@kubb/plugin-axios@820

@kubb/plugin-cypress

npm i https://pkg.pr.new/@kubb/plugin-cypress@820

@kubb/plugin-faker

npm i https://pkg.pr.new/@kubb/plugin-faker@820

@kubb/plugin-fetch

npm i https://pkg.pr.new/@kubb/plugin-fetch@820

@kubb/plugin-mcp

npm i https://pkg.pr.new/@kubb/plugin-mcp@820

@kubb/plugin-msw

npm i https://pkg.pr.new/@kubb/plugin-msw@820

@kubb/plugin-react-query

npm i https://pkg.pr.new/@kubb/plugin-react-query@820

@kubb/plugin-redoc

npm i https://pkg.pr.new/@kubb/plugin-redoc@820

@kubb/plugin-swr

npm i https://pkg.pr.new/@kubb/plugin-swr@820

@kubb/plugin-ts

npm i https://pkg.pr.new/@kubb/plugin-ts@820

@kubb/plugin-vue-query

npm i https://pkg.pr.new/@kubb/plugin-vue-query@820

@kubb/plugin-zod

npm i https://pkg.pr.new/@kubb/plugin-zod@820

commit: 5264b05

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Size Change: +764 B (+0.25%)

Total Size: 312 kB

📦 View Changed
Filename Size Change
packages/plugin-axios/dist/index.cjs 15.5 kB +210 B (+1.37%)
packages/plugin-axios/dist/index.js 15 kB +215 B (+1.45%)
packages/plugin-fetch/dist/index.cjs 15.5 kB +212 B (+1.38%)
packages/plugin-fetch/dist/index.js 15 kB +217 B (+1.46%)
packages/plugin-react-query/dist/index.cjs 17.8 kB -16 B (-0.09%)
packages/plugin-react-query/dist/index.js 17.2 kB -17 B (-0.1%)
packages/plugin-swr/dist/index.cjs 12 kB -12 B (-0.1%)
packages/plugin-swr/dist/index.js 11.4 kB -11 B (-0.1%)
packages/plugin-vue-query/dist/index.cjs 14.8 kB -17 B (-0.11%)
packages/plugin-vue-query/dist/index.js 14.3 kB -17 B (-0.12%)
ℹ️ View Unchanged
Filename Size
packages/plugin-axios/dist/rolldown-runtime-C0LytTxp.js 168 B
packages/plugin-cypress/dist/index.cjs 5.71 kB
packages/plugin-cypress/dist/index.js 5.62 kB
packages/plugin-cypress/dist/rolldown-runtime-C0LytTxp.js 168 B
packages/plugin-faker/dist/index.cjs 14.5 kB
packages/plugin-faker/dist/index.js 14.4 kB
packages/plugin-faker/dist/rolldown-runtime-C0LytTxp.js 168 B
packages/plugin-fetch/dist/rolldown-runtime-C0LytTxp.js 168 B
packages/plugin-mcp/dist/index.cjs 10 kB
packages/plugin-mcp/dist/index.js 9.54 kB
packages/plugin-mcp/dist/rolldown-runtime-C0LytTxp.js 168 B
packages/plugin-msw/dist/index.cjs 5.81 kB
packages/plugin-msw/dist/index.js 5.72 kB
packages/plugin-msw/dist/rolldown-runtime-C0LytTxp.js 168 B
packages/plugin-react-query/dist/rolldown-runtime-C0LytTxp.js 168 B
packages/plugin-redoc/dist/index.cjs 2.21 kB
packages/plugin-redoc/dist/index.js 1.74 kB
packages/plugin-redoc/dist/rolldown-runtime-C0LytTxp.js 168 B
packages/plugin-swr/dist/rolldown-runtime-C0LytTxp.js 168 B
packages/plugin-ts/dist/index.cjs 23.8 kB
packages/plugin-ts/dist/index.js 23.1 kB
packages/plugin-ts/dist/rolldown-runtime-CNktS9qV.js 291 B
packages/plugin-vue-query/dist/rolldown-runtime-C0LytTxp.js 168 B
packages/plugin-zod/dist/index.cjs 19.7 kB
packages/plugin-zod/dist/index.js 19.6 kB
packages/plugin-zod/dist/rolldown-runtime-C0LytTxp.js 168 B

compressed-size-action

… in withUnwrap

CI caught this on this repo's own examples/axios: an operation with no
documented 2xx response (deletePet, 400 only) failed to typecheck.
withUnwrap(request(callConfig)) inferred its generic from the client's
own CallResult type, so the outer `as Unwrappable<RequestResult<...>>`
cast carried two incompatible `.then` overloads and TypeScript rejected
it as too narrow a conversion.

Casting the call to RequestResult first, then wrapping, keeps
withUnwrap's generic inferred as RequestResult directly, matching what
the code did before this feature and avoiding the second cast entirely.
Reproduced the exact failure in isolation, confirmed the fix, then
verified end-to-end with a real `pnpm generate` against a built
plugin-axios (this sandbox has no prebuilt dist, so pnpm generate
otherwise fails to resolve the plugin).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012DSVW79h2FdFDkoWWr6PwN
@dosubot

dosubot Bot commented Sep 1, 2026

Copy link
Copy Markdown

📄 Knowledge review

🆕 New pages

1 new page was drafted from this PR.

Page Library
Using Generated Client Functions - Response Handling and unwrap() Kubb's Space

Leave Feedback Ask Dosu about plugins Add Dosu to your team

stijnvanhulle pushed a commit that referenced this pull request Sep 1, 2026
…a shared runtime helper

/simplify's reuse, simplification, and altitude passes all converged
on the same finding: the generated return statement inlined
`config.throwOnError ?? true ? result.data : result` as a literal
string, restating a default the runtime already applies internally,
once per operation across every spec. The sibling `text/event-stream`
path already centralizes its post-processing in a runtime function
(toEventStream); this does the same for returnType: 'data' with a new
unwrapResult(promise, throwOnError) exported from both templates.

Verified with a real `pnpm generate` against a built plugin-axios,
covering the no-2xx-response edge case that broke CI on PR #820.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012DSVW79h2FdFDkoWWr6PwN
/simplify: withUnwrap is a pure function over a promise, so most of
its tests don't need the axios/fetch client mocking machinery. Call
it directly with a resolved/rejected promise instead. The one case
that genuinely exercised the client (rejection propagation) is
simplified to a plain rejected promise, since that's standard Promise
behavior withUnwrap doesn't special-case.

Also reviewed the diff for reuse, efficiency, and altitude issues
(three of four /simplify agents hit a rate limit and had to be
redone by hand): no other findings. A suggested "UnwrapResult
duplicates SuccessOf" reuse finding was a false positive — they
operate on different type shapes (the OpenAPI responses record vs.
the resolved RequestResult union) and can't share an implementation
without coupling withUnwrap's generic to spec-shape information it
doesn't need.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012DSVW79h2FdFDkoWWr6PwN
@coldtea-pr-lens

coldtea-pr-lens Bot commented Sep 2, 2026

Copy link
Copy Markdown

◈ PR Lens

🟢 +0 new · 🟠 ~7 changed · 🔴 -0 removed · 2 flows · 11 files · commit bf5fa26


Architecture

Architecture diagram for kubb-labs/plugins at bf5fa26

7 components touched across 5 lanes.

Open full size


Inside the changed components — 3 views

Component view — Internals Client Builder

Internal modules in @⁠internals/client building unwrappable function signatures, return statements, and SDK class methods.

Architecture view of Component view — Internals Client Builder in kubb-labs/plugins

Component view — Fetch Plugin Runtime

The fetch client plugin modules, runtime template with withUnwrap and Unwrappable types, and request serializers.

Architecture view of Component view — Fetch Plugin Runtime in kubb-labs/plugins

Component view — Axios Plugin Runtime

The Axios client plugin modules, runtime template with withUnwrap and Unwrappable types, and request serializers.

Architecture view of Component view — Axios Plugin Runtime in kubb-labs/plugins

Data flow

Data flow diagram for kubb-labs/plugins at bf5fa26

Generating unwrappable client operations · Resolving operation result via unwrap

Open full size


The other flows — 1 sequence

Resolving operation result via unwrap

Sequence diagram of Resolving operation result via unwrap in kubb-labs/plugins

Drill down
Internal Foundations — 3 components
🟡 CHANGED Internals Client Builder

Generates client operation functions and SDK classes, wrapping return statements in withUnwrap and annotating return types with Unwrappable.

🟡 CHANGED Client & SDK Generators

JSX-based generators for operation functions and SDK classes, importing withUnwrap and Unwrappable into generated code.

🟡 CHANGED AST Signature & Return Builders

Builds function signatures returning Unwrappable<RequestResult<...>> and return statements wrapping request calls in withUnwrap().

Client & SDK Plugins — 4 components
🟡 CHANGED Plugin Fetch

Generates native fetch client operations and provides the fetch runtime template with withUnwrap and Unwrappable helper types.

🟡 CHANGED Plugin Axios

Generates Axios client operations and provides the axios runtime template with withUnwrap and Unwrappable helper types.

🟡 CHANGED Fetch Runtime Template

Runtime client template copied into generated output, exporting withUnwrap() and Unwrappable/UnwrapResult types.

🟡 CHANGED Axios Runtime Template

Runtime client template copied into generated output, exporting withUnwrap() and Unwrappable/UnwrapResult types.


View

  • Architecture lens
  • Data flow lens
  • Expand every detail
  • Show unchanged neighbours

Tip

Would you rather run it from CI on a key of your own? Add .github/workflows/pr-lens.yml with coldteadotai/pr-lens/packages/action@v0 and a model key in your repository secrets, say GEMINI_API_KEY. The Action asks Gemini by default, or OpenAI and any endpoint speaking /chat/completions through its provider input.

🪧 More tips
  • Run PR Lens on your own machine: npx skills add coldteadotai/pr-lens installs the agent skill. Then tell your coding agent: "Diagram the change you just made with PR Lens and attach it to the pull request."
  • Draw a diff before it is even a pull request: npx @coldtea/pr-lens-cli analyze --base origin/main reads the diff with your own model key, and npx @coldtea/pr-lens-cli render .pr-lens/graph.json draws the same lenses on your machine.
  • The boxes under View are live. Tick Architecture lens or Data flow lens to choose which diagrams appear, or Expand every detail to open every drill-down at once. The comment redraws in place a few seconds later.
  • Show unchanged neighbours lists the components this change did not touch alongside the ones it did, so the drill-down shows what the changed code sits next to.
  • GitHub will not let you zoom an image in a comment. The link under each diagram opens it full size on a page of its own, where you can.
  • The CLI's render picks up .github/pr-lens.yml automatically and applies your corrections (renames, exclusions, lane pins) at draw time.
  • PR Lens is free for open source. A star on the repository is what keeps it going.
  • Push a new commit and the whole comment re-renders for the new head. An older run never overwrites a newer one, so a slow render cannot put a stale diagram back.
  • The diagrams follow your GitHub theme, so dark mode gets the dark render and light mode the light one, and the moving dots show this pull request's data in motion.

◈ Rendered by PR Lens · crafted with ❤️ by the Coldtea team · Something drawn wrong?

@coderabbitai

coderabbitai Bot commented Sep 2, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: af6bd523-420f-4310-9d1f-b007608017ff

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

claude and others added 2 commits September 2, 2026 17:46
…) in generated hooks

Query and mutation bodies destructured `{ data }` off the client call by hand. Every axios/fetch
call already carries unwrap(), so the generated hooks call it directly instead, one line shorter
and consistent with how a caller reaches for the bare success body outside a hook.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012DSVW79h2FdFDkoWWr6PwN
@stijnvanhulle
stijnvanhulle merged commit 5b1ccb8 into main Sep 2, 2026
16 checks passed
@stijnvanhulle
stijnvanhulle deleted the claude/client-unwrap-method branch September 2, 2026 19:04
stijnvanhulle pushed a commit that referenced this pull request Sep 4, 2026
Combines main's unconditional unwrap()/Unwrappable (from #820) with this
branch's returnType option: the default 'full' path now wraps with
withUnwrap so query/mutation hooks call unwrap() instead of destructuring,
and 'data' still routes through unwrapResult. Generated snapshots and
examples are stale after the merge and are regenerated next.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012DSVW79h2FdFDkoWWr6PwN
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants